#pragma once
#include <functional>
#include <algorithm>
#include <list>

#ifdef _MSC_VER
using namespace std;
#else
using namespace std::tr1;
#endif

/**  
 *  @addtogroup  Events
 * @{
 **/

template <typename TSignature>
class SignalBase;

class SignalRef
{
public:
	SignalRef() : mRefs(1), alive(true) {}

	void retain() {++mRefs;}
	void release() 
	{
		--mRefs; 
		if (mRefs == 0) 
		{
			delete this;
		}
	}

	bool alive;

	unsigned int references() const {return mRefs;}
private:
	unsigned int mRefs;
};

//====== Slot ======

/**
* @brief   Slot.
* 			
* @tparam  TSignature  method signature
* 						
*/
template < typename TSignature>
class Slot
{
	friend class SignalBase < TSignature>;
public:
	Slot();
	Slot(const Slot< TSignature >& other);
	~Slot() { if (mRef) mRef->release(); mRef = nullptr;}

	/// Query if this slot is connected to signal.
	///
	/// @return	true if connected, false if not.
	bool isConnected() const;

	/// Disconnects slot from signal.
	/// 		 Do nothing if slot is not connected.
	void disconnect();

	/// Reconnects slot to signal.
	/// Slot should be disconnected. Do nothing if slot is connected or it has never been
	/// connected.
	void reconnect();

	/// Equality operator.
	/// Compares two slots. Two slots are equal if they are connected to the same signal and
	/// have the same identifier. Comparison between slots of different signature is not
	/// allowed.
	///
	/// @param	other	Slot to compare with.
	///
	/// @return	true if the parameters are considered equivalent.
	bool operator == (const Slot< TSignature >& other);

	/**
	* @brief   Inequality operator.
	* 			Compares two slots.
	* 			Two slots are unequal if they are connected to different signals or have
	* 			different identifiers.
	* 			Comparison between slots of different signature is not allowed.
	*
	* @param   other   Slot to compare with.
	*
	* @return  true if the parameters are not considered equivalent.
	*/
	bool operator != (const Slot< TSignature >& other);

	Slot < TSignature >& operator = (const Slot < TSignature >& other)
	{
		mSignal = other.mSignal;
		mHandler = other.mHandler;
		mID = other.mID;
		mIsConnected = other.mIsConnected;

		if (mRef)
			mRef->release();

		mRef = other.mRef;
		if (mRef) 
			mRef->retain();

		return *this;
	}

	SignalBase < TSignature >*     mSignal;
	unsigned int                   mID;

	function < TSignature >        mHandler;
	bool                           mIsConnected;

	SignalRef*                      mRef;
	bool isRefAlive() const { return (mRef != nullptr) && mRef->alive;}
};

// ======= SignalBase =========

/**
* @brief   Base signal.
* 			Implements base functions of signal-slot system
*/
template <typename TSignature>
class SignalBase
{
public:
	SignalBase();
	virtual ~SignalBase ();
	typedef Slot<TSignature> slot_t;
	typedef function < TSignature > function_t;

	/**
	* @brief   Connects function as new slot.
	*
	* @param   handler The function to connect.
	*
	* @return  Slot.
	*/
	slot_t connect( function_t handler);

	void   reconnect(Slot < TSignature >& slot);

	void disconnect(slot_t& slot );

	void disconnect(); // all of 'em

	//protected:
	unsigned int mIDCounter;

	std::list < slot_t > mSlots;
	std::list < slot_t > mSlotsToConnect;
	std::list < slot_t > mSlotsToDisconnect;
	SignalRef* mRef;

	void doConnect();
	void doDisconnect();
};

template <typename TSignature>
SignalBase<TSignature>::~SignalBase()
{
	doDisconnect();mRef->alive = false; 
	mRef->release();
	mRef = nullptr;
}


//======= SignalBase implementation ====

template <typename TSignature>
SignalBase<TSignature>::SignalBase() : mIDCounter(0), mRef(new SignalRef())
{

}

template < typename TSignature >
Slot < TSignature > SignalBase < TSignature >::connect(function < TSignature > handler)
{
	Slot < TSignature > slot;
	slot.mID = mIDCounter++;
	slot.mSignal = this;
	slot.mHandler = handler;
	slot.mIsConnected = true;
	slot.mRef = this->mRef;
	this->mRef->retain(); // for slot

	mSlotsToConnect.push_back(slot);
	return slot;
}

template < typename TSignature >
void SignalBase <TSignature>::disconnect(Slot<TSignature>& slot )
{
	if (slot.mIsConnected)
	{
		slot.mIsConnected = false;
		mSlotsToDisconnect.push_back(slot);
	}
}

template <typename TSignature>
void SignalBase<TSignature>::doDisconnect()
{
	while (!mSlotsToDisconnect.empty())
	{
		slot_t slot = mSlotsToDisconnect.front();
		mSlots.remove(slot);
		mSlotsToDisconnect.pop_front();
	}
}

template <typename TSignature>
void SignalBase<TSignature>::doConnect()
{
	while (!mSlotsToConnect.empty())
	{
		slot_t slot = mSlotsToConnect.front();
		mSlots.push_back(slot);
		mSlotsToConnect.pop_front();
	}
}

template < typename TSignature >
void SignalBase <TSignature>::disconnect( )
{
	for ( auto it = this->mSlots.begin(); it != this->mSlots.end(); ++it)
	{
		auto slot = *it;
		slot.disconnect();
	}
}

template <typename TSignature>
void SignalBase<TSignature>::reconnect( Slot < TSignature >& slot )
{
	if (!slot.mIsConnected)
	{
		mSlotsToConnect.push_back(slot);
		slot.mIsConnected = true;
	}
}

//=========== Connection implementation =================
template < typename TSignature >
Slot < TSignature > ::Slot()
{
	mIsConnected = false;
	mID = 0;
	mSignal = nullptr;
	mRef = nullptr;
}

template < typename TSignature>
bool Slot<TSignature>::operator==( const Slot< TSignature >& other )
{
	return (mSignal == other.mSignal) && (mID == other.mID);
}

template < typename TSignature>
bool Slot<TSignature>::operator!=( const Slot< TSignature >& other )
{
	return (mSignal != other.mSignal) || (mID != other.mID);
}

template < typename TSignature>
Slot<TSignature>::Slot( const Slot< TSignature >& other )
{
	mSignal = other.mSignal;
	mHandler = other.mHandler;
	mID = other.mID;
	mIsConnected = other.mIsConnected;
	mRef = other.mRef;
	if (mRef) mRef->retain();
}

template < typename TSignature >
void Slot< TSignature >::disconnect()
{
	if (this->mIsConnected && this->isRefAlive())
	{
		mSignal->disconnect(*this);
	}
}

template < typename TSignature>
void Slot<TSignature>::reconnect()
{
	if (!this->mIsConnected && this->isRefAlive())
	{
		this->mSignal->reconnect(*this);
	}
}

template < typename TSignature>
bool Slot < TSignature >::isConnected() const
{
	return this->mIsConnected && this->isRefAlive();
}

// ===== dirty tricks ====

/**
* @brief   Signal.
* 			
* @tparam      TSignature      Signature of signal method.
*/
template <typename TSignature >
class Signal : public SignalBase < TSignature >
{
};

// 0 parameters specialization
template <>
class Signal < void (void) > : public SignalBase < void (void) > 
{
	typedef Slot< void (void) > slot_t;
	typedef SignalBase < void (void) > base_t;
public:
	void operator () (void)    
	{
		doConnect();
		doDisconnect();
		for (auto it = this->mSlots.begin(); it != this->mSlots.end(); ++it )
		{
			it->mHandler();
		}
	}
};

// 1 parameter specialization
template < typename TArg >
class Signal < void (TArg) > : public SignalBase < void (TArg) > 
{
	typedef SignalBase < void (TArg) > base_t;
public:
	void operator () (TArg arg)    
	{
		this->doConnect();
		this->doDisconnect();
		for (auto it = this->mSlots.begin(); it != this->mSlots.end(); ++it )
		{
			it->mHandler(arg);
		}
	}
};

// 2 parameters specialization
template < typename TArg1, typename TArg2 >
class Signal < void (TArg1, TArg2) > : public SignalBase < void (TArg1, TArg2) > 
{
	typedef SignalBase < void (TArg1, TArg2) > base_t;
public:
	void operator () (TArg1 a1, TArg2 a2)    
	{
		this->doConnect();
		this->doDisconnect();
		for (auto it = this->mSlots.begin(); it != this->mSlots.end(); ++it )
		{
			it->mHandler(a1, a2);
		}
	}
};

// 3 parameters specialization
template < typename TArg1, typename TArg2, typename TArg3 >
class Signal < void (TArg1, TArg2, TArg3) > : public SignalBase < void (TArg1, TArg2, TArg3) > 
{
	typedef SignalBase < void (TArg1, TArg2, TArg3) > base_t;
public:
	void operator () (TArg1 a1, TArg2 a2, TArg3 a3)    
	{
		this->doConnect();
		this->doDisconnect();
		for (auto it = this->mSlots.begin(); it != this->mSlots.end(); ++it )
		{
			it->mHandler(a1, a2, a3);
		}
	}
};
// helpers
template <class TClass>
Slot < void (void) > connect(Signal < void (void) >& signal, TClass* obj, void (TClass::*foo)(void))
{
	return signal.connect(bind(foo, obj)); // 2 velorth: use of placeholders::_1 forwards 1st func's arg to be passed at function obj caller. void - is not a valid arg type!!! you should burn in fire.
}

template <class TClass, typename TArg1>
Slot < void (TArg1) > connect(Signal < void (TArg1) >& signal, TClass* obj, void (TClass::*foo)(TArg1))
{
	return signal.connect(bind(foo, obj, placeholders::_1));
}

template <class TClass, typename TArg1, typename TArg2>
Slot < void (TArg1, TArg2) > connect(Signal < void (TArg1, TArg2) >& signal, TClass* obj, void (TClass::*foo)(TArg1, TArg2))
{
	return signal.connect(bind(foo, obj, placeholders::_1, placeholders::_2));
}

template <class TClass, typename TArg1, typename TArg2, typename TArg3>
Slot < void (TArg1, TArg2, TArg3) > connect(Signal < void (TArg1, TArg2, TArg3) >& signal, TClass* obj, void (TClass::*foo)(TArg1, TArg2, TArg3))
{
	return signal.connect(bind(foo, obj, placeholders::_1, placeholders::_2, placeholders::_3));
}

/** 
* @}
**/
